package com.ruge.tool.compress;

import com.ruge.tool.date.DateTool;
import com.ruge.tool.file.FilenameTool;
import com.ruge.tool.str.StringTool;
import lombok.extern.slf4j.Slf4j;
import net.lingala.zip4j.ZipFile;
import net.sf.sevenzipjbinding.ExtractOperationResult;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * description: 压缩解压工具类
 * create time at 2022/7/17 23:08
 *
 * @author alice.ruge
 * @since 0.0.3
 */
@Slf4j
public class CompressTool {

    /**
     * 文件拓展名
     */
    public static final String EXT_ZIP = ".zip";
    public static final String EXT_RAR = ".rar";
    public static final String EXT_7Z = ".7z";
    public static final String PASSWORD = "password";

    /**
     * zip解压
     *
     * @param rootPath      文件完整路径，不含文件名
     * @param sourceRarPath 单独的文件名及其后缀
     * @param destDirPath   解压目标文件名
     * @param passWord      密码
     * @return 解压结果
     */
    public static String unZip(String rootPath, String sourceRarPath, String destDirPath, String passWord) {
        ZipFile zipFile;
        String result = "";
        try {
            String filePath = rootPath + sourceRarPath;
            if (StringTool.isNotBlank(passWord)) {
                zipFile = new ZipFile(filePath, passWord.toCharArray());
            } else {
                zipFile = new ZipFile(filePath);
            }
            zipFile.setCharset(Charset.defaultCharset());
            if (StringTool.isNotBlank(destDirPath)) {
                zipFile.extractAll(rootPath + destDirPath);
            } else {
                zipFile.extractAll(rootPath);
            }
        } catch (Exception e) {
            log.error("unZip error", e);
            return e.getMessage();
        }
        return result;
    }

    /**
     * rar解压
     *
     * @param rootPath      文件完整路径，不含文件名
     * @param sourceRarPath 单独的文件名及其后缀
     * @param destDirPath   解压目标文件名
     * @param passWord      密码
     * @return 解压结果
     */
    public static String unRar(String rootPath, String sourceRarPath, String destDirPath, String passWord) {
        String rarDir = rootPath + sourceRarPath;
        String outDir;
        if (StringTool.isNotBlank(destDirPath)) {
            outDir = rootPath + destDirPath + File.separator;
        } else {
            outDir = rootPath + File.separator;
        }
        RandomAccessFile randomAccessFile = null;
        IInArchive inArchive = null;
        try {
            // 第一个参数是需要解压的压缩包路径，第二个参数参考JdkAPI文档的RandomAccessFile
            randomAccessFile = new RandomAccessFile(rarDir, "r");
            if (StringTool.isNotBlank(passWord)) {
                inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile), passWord);
            } else {
                inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile));
            }

            ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
            for (final ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
                final int[] hash = new int[]{0};
                if (!item.isFolder()) {
                    ExtractOperationResult result;
                    final long[] sizeArray = new long[1];

                    File outFile = new File(outDir + item.getPath());
                    File parent = outFile.getParentFile();
                    if ((!parent.exists()) && (!parent.mkdirs())) {
                        continue;
                    }
                    if (StringTool.isNotBlank(passWord)) {
                        result = item.extractSlow(data -> {
                            try {
                                IOUtils.write(data, new FileOutputStream(outFile, true));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            hash[0] ^= Arrays.hashCode(data); // Consume data
                            sizeArray[0] += data.length;
                            return data.length; // Return amount of consumed
                        }, passWord);
                    } else {
                        result = item.extractSlow(data -> {
                            try {
                                IOUtils.write(data, new FileOutputStream(outFile, true));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            hash[0] ^= Arrays.hashCode(data); // Consume data
                            sizeArray[0] += data.length;
                            return data.length; // Return amount of consumed
                        });
                    }

                    if (result == ExtractOperationResult.OK) {
                        log.info("解压rar成功...." + String.format("%9s | %10s | %s", DateTool.formatDateTime(), sizeArray[0]/1024, item.getPath()));
                    } else if (StringTool.isNotBlank(passWord)) {
                        log.error("解压rar成功：密码错误或者其他错误...." + result);
                        return "password";
                    } else {
                        return "rar error";
                    }
                }
            }

        } catch (Exception e) {
            log.error("unRar error", e);
            return e.getMessage();
        } finally {
            try {
                assert inArchive != null;
                inArchive.close();
                randomAccessFile.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    /**
     * 7z解压
     *
     * @param rootPath      文件完整路径，不含文件名
     * @param sourceRarPath 单独的文件名及其后缀
     * @param destDirPath   解压目标文件名
     * @param passWord      密码
     * @return 解压结果
     */
    private static String un7z(String rootPath, String sourceRarPath, String destDirPath, String passWord) {
        try {
            //获取当前压缩文件
            File srcFile = new File(rootPath + sourceRarPath);
            // 判断源文件是否存在
            if (!srcFile.exists()) {
                throw new Exception(srcFile.getPath() + "所指文件不存在");
            }
            //开始解压
            SevenZFile zIn;
            if (StringTool.isNotBlank(passWord)) {
                zIn = new SevenZFile(srcFile, passWord.toCharArray());
            } else {
                zIn = new SevenZFile(srcFile);
            }

            SevenZArchiveEntry entry;
            File file;
            while ((entry = zIn.getNextEntry()) != null) {
                if (!entry.isDirectory()) {
                    if (StringTool.isNotBlank(destDirPath)) {
                        file = new File(rootPath + destDirPath, entry.getName());
                    } else {
                        file = new File(rootPath, entry.getName());
                    }
                    if (!file.exists()) {
                        //创建此文件的上级目录
                        new File(file.getParent()).mkdirs();
                    }
                    OutputStream out = new FileOutputStream(file);
                    BufferedOutputStream bos = new BufferedOutputStream(out);
                    int len;
                    byte[] buf = new byte[1024];
                    while ((len = zIn.read(buf)) != -1) {
                        bos.write(buf, 0, len);
                    }
                    // 关流顺序，先打开的后关闭
                    bos.close();
                    out.close();
                }
            }

        } catch (Exception e) {
            log.error("un7z is error", e);
            return e.getMessage();
        }
        return "";
    }

    /**
     * 提取到当前位置
     *
     * @param path 文件路径
     * @return 提取结果
     */
    public static Map<String, Object> unCompress(String path) {
        String sysPath = FilenameTool.separatorsToSystem(path);
        String rootPath = FilenameTool.getFullPath(sysPath);
        String sourcePath = FilenameTool.getName(sysPath);
        return unCompress(rootPath, sourcePath, null, null);
    }

    /**
     * 提取到当前位置有密码
     *
     * @param path     文件路径
     * @param passWord 密码
     * @return 提取结果
     */
    public static Map<String, Object> unCompress(String path, String passWord) {
        String sysPath = FilenameTool.separatorsToSystem(path);
        String rootPath = FilenameTool.getFullPath(sysPath);
        String sourcePath = FilenameTool.getName(sysPath);
        return unCompress(rootPath, sourcePath, null, passWord);
    }

    public static Map<String, Object> unCompress(String rootPath, String sourcePath, String destDirPath, String passWord) {
        Map<String, Object> resultMap = new HashMap<>(16);
        String result = "";
        if (sourcePath.toLowerCase().endsWith(EXT_ZIP)) {
            //Wrong password!
            result = unZip(rootPath, sourcePath, destDirPath, passWord);
        } else if (sourcePath.toLowerCase().endsWith(EXT_RAR)) {
            //java.security.InvalidAlgorithmParameterException: password should be specified
            result = unRar(rootPath, sourcePath, destDirPath, passWord);
            System.out.println(result);
        } else if (sourcePath.toLowerCase().endsWith(EXT_7Z)) {
            //PasswordRequiredException: Cannot read encrypted content from G:\ziptest\11111111.7z without a password
            result = un7z(rootPath, sourcePath, destDirPath, passWord);
        }

        resultMap.put("resultMsg", 1);
        if (StringTool.isNotBlank(result)) {
            if (result.contains(PASSWORD)) {
                resultMap.put("resultMsg", 2);
            }
            if (!result.contains(PASSWORD)) {
                resultMap.put("resultMsg", 3);
            }
        }
        resultMap.put("files", null);
        return resultMap;
    }

}
